/*
SteelNzb
Copyright (C) 2008  Wessel v. Staal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "global.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#if defined(WIN32) || defined(WINCE)
#include <winsock2.h>
#include <windows.h>
#define stricmp _stricmp
#define unlink(x) DeleteFileA(x)
#ifndef WINCE
#include <direct.h>
#endif
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <dirent.h>
#define stricmp strcasecmp
#endif

#include "socket.h"

typedef struct receive_buffer
{
	char* data;
	int len;
	int extracted;
} receive_buffer;

void* socket_createBuffer()
{
	receive_buffer* buf = NULL;

	if ( (buf = malloc(sizeof(receive_buffer))) == NULL )
	{
		perror("socket_createBuffer");
		exit(1);
	}

	memset(buf, 0, sizeof(receive_buffer));

	return buf;
}

void socket_freeBuffer(void*buffer)
{
	int size = 0;
	receive_buffer* buf = buffer;

	socket_emptyBuffer(buffer, &size);
	free(buf->data);
	free(buf);
}
/**
	Returns remaining data left in buffer used by socket_receiveLine
	@param buf receive_buffer struct containing buffer information
	@param size Out: bytes remaining
	@returns pointer to data
*/
char* socket_emptyBuffer(void* buffer, int* size)
{
	receive_buffer* buf = buffer;

	if ( buf->extracted > 0 )
	{
		buf->len -= buf->extracted; 
		
		if ( buf->len > 0 )
		{
			if ( memmove(buf->data, buf->data+buf->extracted, buf->len) == NULL )
				perror("memmove");
		}
		
		buf->extracted = 0;
	}
	
	if ( size != NULL )
		*size = buf->len;
	
	return buf->data;
}

/**
	Receives line of data from socket descriptor
	@param socket socket descriptor
	@param buf receive_buffer struct containing buffer information
	@param size Out: bytes read
	@param timeout Variable defining whether receive can timeout (1: timeout, else: no timeout)
	@returns error code
*/
char* socket_receiveLine(int socket, void* receivebuffer, int* size, int timeout)
{
	int bytes_read = 0;
	char* lf = NULL;
	int retval = 0;
	receive_buffer* buf = receivebuffer;
	char buffer[MAX_PACKET_SIZE];
	
	/* Infinite loop, breaks when:
	 * - newline is available in socket buffer
	 * - timeout occurs when waiting for data
	 * - some other socket error occurs
	 * */
	
	/* move data which is still in the buffer to the start of the data */
	if ( buf->extracted > 0 )
	{
		buf->len -= buf->extracted; 
		
		if ( buf->len > 0 )
		{
			if ( memmove(buf->data, buf->data+buf->extracted, buf->len) == NULL )
				perror("memmove");
		}
		
		buf->extracted = 0;
	}
	
	memset(buffer, 0, sizeof(buffer));
	
	do
	{
		if ( (lf = memchr(buf->data, '\n', buf->len)) != NULL  )
			break;
		
		/* Wait until data is available */
		if (timeout == 1 && (retval = socket_waitForData(socket)) != 0)
			return NULL;

		/* extract data from buffer */
		if ((bytes_read = recv(socket, buffer, sizeof(buffer), 0)) > 0)
		{
			if ( buf->len+bytes_read > MAX_RECEIVELINE_BUFFER )
			{
				perror("socket::receiveLine, buffer to large");
				return NULL;
			}

			if ( (buf->data = realloc(buf->data, buf->len+bytes_read)) == NULL )
			{
				perror("realloc");
				exit(1);
			}
			
			/* copy data into our own buffer */
			memcpy(buf->data+buf->len, buffer, bytes_read);
			
			buf->len += bytes_read;
		}
		else
		{
			return NULL;
		}
	}
	while (1);
	
	/* extract line from socket buffer */
	*size = (int)(lf-buf->data + 1);

	buf->extracted = *size;
		
	return buf->data;
}

/**
	Waits until data is available for socket
	@param socket socket descriptor
	@returns error code
*/
int socket_waitForData(int socket)
{
	int retval = 0;
	struct timeval timeval;
	fd_set fdset;

	/* Wait max 30 seconds (Defined in protocol specification) */
	timeval.tv_sec = RECEIVE_TIMEOUT; 
	timeval.tv_usec = 0;
	//timeval.tv_usec = RECEIVE_TIMEOUT*1000000;

	FD_ZERO(&fdset);
	FD_SET((unsigned int)socket, &fdset);

	/* Use select to wait for data to arrive */
	retval = select(socket+1, &fdset, NULL, NULL, &timeval);
	
	/* Indicate if there was an error */
	if ( retval == 0 )
		return SOCKET_ERROR_TIMEOUT;
	else if ( retval == -1 )
		return GENERAL_SOCKET_ERROR;
	else
		return 0;
}

/**
	Receives data from socket
	@param socket socket descriptor
	@param buffer buffer to read to
	@param size In: size of buffer, Out: bytes read
	@param timeout Variable defining whether receive can timeout (1: timeout, else: no timeout)
	@returns error code
*/
int socket_receive(int socket, char* buffer, int* size, int timeout)
{
	int bytes_read = 0;
	int retval = 0;

	/* Wait until data is available */
	if ( timeout == 1 && (retval = socket_waitForData(socket)) != 0 )
		return retval;

	memset(buffer, 0, *size);

	/* Get the available data */
	if ( (bytes_read = recv(socket, buffer, *size, 0)) == 0 )
		return GENERAL_SOCKET_ERROR;
	
	/* assign bytes read to size parameter */
	*size = bytes_read;

	return 0;
}

/**
	Send data to socket
	@param socket socket descriptor
	@param buffer buffer to send
	@param size size of data to be send to socket
	@returns error code
*/
int socket_send(int socket, char* buf, unsigned int size)
{
	int bytes_send = 0;

	do
	{
		bytes_send = send(socket, buf+bytes_send, size-bytes_send, 0);
	}
	while ( bytes_send > 0 );

	return bytes_send != -1;
}

/**
	Connects with the server
	@param host host name of server
	@param port port number of server
	@returns connection descriptor
*/

int socket_connect(const char * host, int port)
{
	int sd, con;
	struct sockaddr_in addr;
	struct hostent *pHost;
	
	/* create tcp socket */
	if ((sd = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		perror("Error creating socket");
		exit(1);
	}
	
	memset(&addr, 0, sizeof(struct sockaddr_in));/* free adres */
	addr.sin_family = AF_INET;
	pHost = (struct hostent *)gethostbyname(host);

	if ( pHost == NULL )
		return -1;

	memcpy(&addr.sin_addr, pHost->h_addr_list[0], sizeof(&addr.sin_addr));
	addr.sin_port = htons((u_short)port);
	
	/* connect with the specified hostname and portnumber */
	if ((con = connect(sd, (struct sockaddr *) &addr, sizeof(addr))) == -1)
	{
		return -1;
	}
	
	return sd;
}

